package cz.martindobias.aladin;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;

import java.text.DateFormat;
import java.util.*;

public class AladinProvider extends ContentProvider {

    public static final UriMatcher URI_MATCHER;

    static {
        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.ForecastTable.RESOURCE, ContentTables.ForecastTable.TYPE);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.AladinTable.RESOURCE, ContentTables.AladinTable.TYPE);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.BitmapTable.RESOURCE, ContentTables.BitmapTable.TYPE);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.PrebaTable.RESOURCE, ContentTables.PrebaTable.TYPE);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.RadarTable.RESOURCE, ContentTables.RadarTable.TYPE);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.VisirTable.RESOURCE, ContentTables.VisirTable.TYPE);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.PPTable.RESOURCE, ContentTables.PPTable.TYPE);
        URI_MATCHER.addURI(ContentTables.AUTHORITY, ContentTables.CountyTable.RESOURCE, ContentTables.CountyTable.TYPE);
    }

    private static final Object RESOURCE_FAILED = new Object();
    private static final Object RESOURCE_LOADING = new Object();

    private Map<Uri, Object> cache = Collections.synchronizedMap(new HashMap<Uri, Object>());
    private Date timestamp = null;
    private Messenger messenger = new Messenger(new CallbackHandler());

    private class CallbackHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Bundle data = msg.getData();
            Uri uri = data.getParcelable(ContentTables.COLUMN_URI);

            switch(msg.what) {
                case AladinBrowserService.MSG_FORECAST:
                case AladinBrowserService.MSG_PREBA:
                    AladinProvider.this.timestamp = new Date();
                    AladinProvider.this.cache.put(uri, data.getString(ContentTables.COLUMN_RESULT));
                    break;

                case AladinBrowserService.MSG_BITMAP:
                    AladinProvider.this.cache.put(Uri.parse(data.getString(AladinBrowserService.URL_EXTRA)), data.getString(ContentTables.COLUMN_RESULT));
                    break;

                case AladinBrowserService.MSG_COUNTY:
                    AladinProvider.this.cache.put(Uri.parse(data.getString(AladinBrowserService.URL_EXTRA)), data.getString(ContentTables.COLUMN_RESULT));
                    break;

                case AladinBrowserService.MSG_IMAGES:
                case AladinBrowserService.MSG_VISIR:
                case AladinBrowserService.MSG_RADAR:
                    AladinProvider.this.cache.put(uri, data.getParcelableArray(ContentTables.COLUMN_RESULT));
                    AladinProvider.this.timestamp = new Date();
                    break;

                case AladinBrowserService.MSG_PP:
                    AladinProvider.this.cache.put(uri, data.getParcelable(ContentTables.COLUMN_RESULT));
                    AladinProvider.this.timestamp = new Date();
                    break;

                case AladinBrowserService.MSG_CLEAR:
                    AladinProvider.this.clearCache();
                    break;

                case AladinBrowserService.MSG_FAILED:
                    AladinProvider.this.cache.put(uri, RESOURCE_FAILED);
                    break;
                default:
                    super.handleMessage(msg);
            }

            if(uri != null) {
                AladinProvider.this.getContext().getContentResolver().notifyChange(uri, null);
            }
        }
    }

    @Override
    public boolean onCreate() {
        this.freeDiskCache();
        return true;
    }

    @Override
    public String getType(Uri uri) {
        switch(URI_MATCHER.match(uri)) {
            case ContentTables.ForecastTable.TYPE:
                return ContentTables.ForecastTable.CONTENT_TYPE;
            case ContentTables.AladinTable.TYPE:
                return ContentTables.AladinTable.CONTENT_TYPE;
            case ContentTables.BitmapTable.TYPE:
                return ContentTables.BitmapTable.CONTENT_TYPE;
            case ContentTables.PrebaTable.TYPE:
                return ContentTables.PrebaTable.CONTENT_TYPE;
            case ContentTables.RadarTable.TYPE:
                return ContentTables.RadarTable.CONTENT_TYPE;
            case ContentTables.VisirTable.TYPE:
                return ContentTables.VisirTable.CONTENT_TYPE;
            case ContentTables.PPTable.TYPE:
                return ContentTables.PPTable.CONTENT_TYPE;
            case ContentTables.CountyTable.TYPE:
                return ContentTables.CountyTable.CONTENT_TYPE;
            default:
                throw new IllegalArgumentException("Unknown URI: " + uri);
        }
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Uri tempUri;
        int uriType = URI_MATCHER.match(uri);
        if((uriType == ContentTables.BitmapTable.TYPE || uriType == ContentTables.CountyTable.TYPE) && selection != null) {
            tempUri = Uri.parse(selection);
        } else {
            tempUri = uri;
        }

        MatrixCursor cursor;
        if(this.cache.containsKey(tempUri)) {
            Object result = this.cache.get(tempUri);
            if(result == RESOURCE_FAILED) {
                return null;
            }
            if(result == RESOURCE_LOADING) {
                return new MatrixCursor(new String[0], 0);
            }

            String ts = this.timestamp != null ? DateFormat.getDateTimeInstance().format(this.timestamp) : "";
            switch(uriType) {
                case ContentTables.ForecastTable.TYPE:
                case ContentTables.BitmapTable.TYPE:
                case ContentTables.CountyTable.TYPE:
                    cursor = new MatrixCursor(new String[]{ContentTables.COLUMN_RESULT, ContentTables.COLUMN_TIMESTAMP});
                    cursor.addRow(new Object[]{result, ts});
                    break;

                case ContentTables.AladinTable.TYPE:
                    ContentValues[] aladinValues = (ContentValues[]) result;
                    cursor = new MatrixCursor(new String[]{ContentTables.COLUMN_RESULT,
                            ContentTables.COLUMN_RESULT_TYPE,
                            ContentTables.COLUMN_SEQUENCE, ContentTables.COLUMN_TIMESTAMP}, 40);
                    AladinProvider.writeValuesToCursor(cursor, aladinValues[0], ContentTables.AladinTable.CATEGORY_TEMPERATURE, ts);
                    AladinProvider.writeValuesToCursor(cursor, aladinValues[1], ContentTables.AladinTable.CATEGORY_PRECIPITATION, ts);
                    AladinProvider.writeValuesToCursor(cursor, aladinValues[2], ContentTables.AladinTable.CATEGORY_WIND, ts);
                    AladinProvider.writeValuesToCursor(cursor, aladinValues[3], ContentTables.AladinTable.CATEGORY_NEBULAE, ts);
                    break;

                case ContentTables.PrebaTable.TYPE:
                    cursor = new MatrixCursor(new String[]{ContentTables.COLUMN_RESULT,
                            ContentTables.COLUMN_RESULT_TYPE,
                            ContentTables.COLUMN_SEQUENCE, ContentTables.COLUMN_TIMESTAMP}, 1);
                    cursor.addRow(new Object[]{result, ContentTables.PrebaTable.CATEGORY_PREBA, 0, ts});
                    break;

                case ContentTables.RadarTable.TYPE:
                    ContentValues[] radarValues = (ContentValues[]) result;
                    cursor = new MatrixCursor(new String[]{ContentTables.COLUMN_RESULT,
                            ContentTables.COLUMN_RESULT_TYPE,
                            ContentTables.COLUMN_SEQUENCE, ContentTables.COLUMN_TIMESTAMP}, 300);
                    AladinProvider.writeValuesToCursor(cursor, radarValues[0], ContentTables.RadarTable.CATEGORY_RADAR, ts);
                    AladinProvider.writeValuesToCursor(cursor, radarValues[1], ContentTables.RadarTable.CATEGORY_RADAR_BASE, ts);
                    AladinProvider.writeValuesToCursor(cursor, radarValues[2], ContentTables.RadarTable.CATEGORY_RADAR_OVERLAY, ts);
                    break;

                case ContentTables.VisirTable.TYPE:
                    ContentValues[] visirValues = (ContentValues[]) result;
                    cursor = new MatrixCursor(new String[]{ContentTables.COLUMN_RESULT,
                            ContentTables.COLUMN_RESULT_TYPE,
                            ContentTables.COLUMN_SEQUENCE, ContentTables.COLUMN_TIMESTAMP}, 50);
                    AladinProvider.writeValuesToCursor(cursor, visirValues[0], ContentTables.VisirTable.CATEGORY_VISIR, ts);
                    AladinProvider.writeValuesToCursor(cursor, visirValues[1], ContentTables.VisirTable.CATEGORY_VISIR_OVERLAY, ts);
                    break;

                case ContentTables.PPTable.TYPE:
                    ContentValues ppValues = (ContentValues) result;
                    cursor = new MatrixCursor(new String[]{ContentTables.COLUMN_RESULT,
                            ContentTables.COLUMN_RESULT_TYPE,
                            ContentTables.COLUMN_SEQUENCE, ContentTables.COLUMN_TIMESTAMP}, 15);
                    AladinProvider.writeValuesToCursor(cursor, ppValues, ContentTables.PPTable.CATEGORY_PP, ts);
                    break;

                default:
                    throw new IllegalArgumentException("Unknown uri: " + uri);
            }
        } else {
            cursor = new MatrixCursor(new String[0], 0);
            this.cache.put(tempUri, RESOURCE_LOADING);

            Intent intent = new Intent(Intent.ACTION_VIEW, uri, this.getContext(), AladinBrowserService.class);
            if((uriType == ContentTables.BitmapTable.TYPE || uriType == ContentTables.CountyTable.TYPE) && selection != null) {
                intent.putExtra(AladinBrowserService.URL_EXTRA, selection);
            }
            intent.putExtra(AladinBrowserService.MESSENGER_EXTRA, this.messenger);
            this.getContext().startService(intent);
        }
        cursor.setNotificationUri(this.getContext().getContentResolver(), uri);
        cursor.moveToFirst();
        return cursor;
    }

    private static void writeValuesToCursor(MatrixCursor cursor, ContentValues values, String type, String timestamp) {
        for(Map.Entry<String, Object> entry : values.valueSet()) {
            cursor.addRow(new Object[]{entry.getValue(), type, entry.getKey(), timestamp});
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        throw new IllegalArgumentException("All resources read-only: " + uri);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int result = this.cache.size();
        this.freeDiskCache();
        return result;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        throw new IllegalArgumentException("All resources read-only: " + uri);
    }

    private void freeDiskCache() {
        Intent intent = new Intent(Intent.ACTION_DELETE, ContentTables.BASE_CONTENT_URI, this.getContext(), AladinBrowserService.class);
        intent.putExtra(AladinBrowserService.MESSENGER_EXTRA, this.messenger);
        this.getContext().startService(intent);
    }

    private void clearCache() {
        Iterator<Map.Entry<Uri, Object>> it = this.cache.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry<Uri, Object> entry = it.next();
            if(entry.getValue() != RESOURCE_LOADING) {
                it.remove();
            }
        }
    }
}
